/*
 * @Description  :
 * @Version      : 0.1
 * @Company      : V-Think Development Team
 * @Author       : KiraVerSace@yeah.net
 * @Date         : 2022-11-21 15:58:45
 * @LastEditTime : 2023-07-20 19:18:47
 */

// ┌───────────────────────────────────────────────────┐
// │    ___            __  __                __        │
// │   / _ )___  ___  / /_/ /  ___  ___ ____/ /__ ____ │
// │  / _  / _ \/ _ \/ __/ /__/ _ \/ _ `/ _  / -_) __/ │
// │ /____/\___/\___/\__/____/\___/\_,_/\_,_/\__/_/    │
// │                                                   │
// └───────────────────────────────────────────────────┘
// References:https://github.com/akospasztor/stm32-bootloader

#include <xCommon.h>
#include <BootLoader.h>
#include <xRingBuffer.h>

void  guiInit(void);
void  guiWriteProgressBar(const uint8_t value, const char *str);

void setup()
{
    xCommonInit();
    loadReadOnlyArea();
    setOTABootLoaderVersion();

    // bootLoaderCRC32Verify(195728);
    logInfo("TotalFlash  [%4d]KB", FLASH_SIZE/1024);
    logInfo("Bootloader  [%4d]KB @[0x%08X]", BOOTLOADER_SIZE_KB, FLASH_BASE);
    logInfo("Application [%4d]KB @[0x%08X]", USR_APP_SIZE_KB,    USR_APP_ADDRESS );

    uint8_t upgradeFlag               = readOTAUpgradeFlag();
    uint8_t JumpToApplicationTryCount = readOTAJumpToApplicationTryCount();
    if ( JumpToApplicationTryCount >= 5)    // 尝试跳转5次失败则从默认的地址下载固件
    {
        logWarning("Jump failed, Re-download from the default...");

        upgradeFlag = OTA_FLAG_FROM_DEFAULT;
    }
    else
    {
        logInfo("Jump to Application Try Count = [%d]", JumpToApplicationTryCount);
    }

    delay(120);     // 等待调试信息打印完毕

    if ((upgradeFlag == OTA_FLAG_CLEAR))  // 并且满足BootLoader->APP的标志位已经被APP清除
    {
        /* 无升级标志位,首先判断APP区有无程序,无则从默认的地址下载固件 */
        if(bootLoaderCheckApplicationExist() == true)
        {
            setOTAJumpToApplicationTryCount();              // 置位是否跳转成功,该标志位会在成功运行APP后被清除,如果并未清除,则表示跳转失败
            setOTAUpgradeFromApplicationTryCount(false);    // 表示成功从Application下发

            /* Launching Application... */
            bootLoaderJumpToApplication();
        }
        else
        {
            setOTAUpgradeFlag(OTA_FLAG_FROM_DEFAULT);

            rebootSystem();
        }
    }
    else
    {
        xScreenPeriphInit();
        guiInit();

        guiWriteProgressBar(0, "Start OTA...");

        if ((xReadOnlyArea.hardwareCode == 100) || (xReadOnlyArea.hardwareCode == 101))
        {
            xBuzzer = PA15;
        }
        else    // [102]=PB14=有源蜂鸣器 [103]=PB14=无源蜂鸣器
        {
            xBuzzer = PB14;
        }
	    pinMode(xBuzzer, OUTPUT);
        digitalWrite(xBuzzer, LOW);

        uint8_t  firmwareDefaultPath[FRAM_OTA_FIRMWARE_URL_SIZE];
        memset(firmwareDefaultPath, 0x00, sizeof(firmwareDefaultPath));

        if((xReadOnlyArea.hardwareCode == 100)  // 芯片均为RG
                || (xReadOnlyArea.hardwareCode == 102)
                    || (xReadOnlyArea.hardwareCode == 103))
        {
            strncpy((char *)firmwareDefaultPath, HWC103_DEFAULT_OTA_URL, strlen(HWC103_DEFAULT_OTA_URL));
        }

        if (xReadOnlyArea.hardwareCode == 801)
        {
            strncpy((char *)firmwareDefaultPath, HWC801_DEFAULT_OTA_URL, strlen(HWC801_DEFAULT_OTA_URL));
        }

        if (xReadOnlyArea.hardwareCode == 103)
        {
            tone(xBuzzer, 2500, 320);
        }
        else
        {
            digitalWrite(xBuzzer, HIGH);
	        delay(320);
	        digitalWrite(xBuzzer, LOW);
        }

        logInfo("[HW%d]Firmware Default:[%s]", xReadOnlyArea.hardwareCode, firmwareDefaultPath);

        bootLoaderInit();
		lte.powerOn();

        if (upgradeFlag == OTA_FLAG_FROM_DEFAULT)
        {
            logInfo("OTA from Default...");

            guiWriteProgressBar(0, "OTA Default...");
            bootLoaderPraseFirmwarePath((char *)firmwareDefaultPath, FRAM_OTA_FIRMWARE_URL_SIZE);
            setOTAFirmwareCRCValue(HWC103_DEFAULT_OTA_CRC);
        }
        else
        {
            logInfo("OTA from APP...");

            guiWriteProgressBar(0, "OTA APP...");

            uint8_t url[FRAM_OTA_FIRMWARE_URL_SIZE];
            memset(url, 0x00, sizeof(url));

            readOTAFirmwareUrl(url);
            /**
             * @brief 如果从FRAM里面解析OTA Url失败,那么就用默认的地址
             */
            if (bootLoaderPraseFirmwarePath((char *)url, FRAM_OTA_FIRMWARE_URL_SIZE) == false)
            {
                bootLoaderPraseFirmwarePath((char *)firmwareDefaultPath, sizeof(firmwareDefaultPath));
                setOTAFirmwareCRCValue(HWC103_DEFAULT_OTA_CRC);
            }
            else
            {
                setOTAUpgradeFromApplicationTryCount(true);      // 尝试次数+1,如果从APP下发的地址下载固件尝试六次依然失败,那么从默认地址下载固件

                uint8_t tryErrorCount = readOTAUpgradeFromApplicationTryCount();
                logDebug("Try [%d] OTA from the APP", tryErrorCount);

                if (tryErrorCount >= 4)
                {
                    setOTAUpgradeFromApplicationTryCount(false);

                    logError("Failed OTA from the APP, Set to Default!");
                    setOTAUpgradeFlag(OTA_FLAG_FROM_DEFAULT);

                    rebootSystem();
                }
            }
        }

        otaParameter.crc32Value = readOTAFirmwareCRCValue();

        logInfo("The firmware CRC32:[0x%08X]", otaParameter.crc32Value);
        logInfo("The firmware path:[%s]", otaParameter.firmwarePath);
        logInfo("Entering BootLoader mode...");
        logInfo("Start to erase the Flash....");

        delayWithLED(1000);
        guiWriteProgressBar(0, "Erase Flash...");

        bootLoaderEraseFlash();
    }
}

/* 状态机相关定义 */
enum StateFSM
{
    FSM_POWER_ON = 1,		// 初始化状态
    FSM_HTTP_HEAD,          // 通过HEAD的方式获取固件大小
    FSM_HTTP_GET_REQUEST,   // 通过GET的方式请求固件
    FSM_HTTP_GET_RESPONSE,  // 通过GET
    FSM_FLASH_WRITE,		// 数据发送状态
};
enum StateFSM stateFSM     = FSM_POWER_ON;
enum StateFSM lastStateFSM = FSM_POWER_ON;
uint8_t errCycleCount = 0;
uint8_t errGetResponseCount  = 0;
uint8_t errGetReRequestCount = 0;

uint8_t  firmwareRxBuffer[OTA_BUFFER_SIZE];
uint16_t firmwareRxCount        = 0;  // 每个 RESPONSE期间的接收计数
uint16_t firmwareExpectRxLength = 0;  // REQUEST后应该接收到的字节总数

xRingBuffer<uint8_t, OTA_BUFFER_SIZE> firmwareRBuffer;

bool isLTEConfigured = false;

void loop()
{
    digitalToggle(LED);


    switch(stateFSM)
    {
        case FSM_POWER_ON:
        {
            logInfo("Power on and init the Cat.1 ...");
            guiWriteProgressBar(0, "LTE Init...");

            if (isLTEConfigured == true)
            {
				lte.powerOn();
                delayWithLED(6000);
            }
            else
            {
				/**
				 * @brief 已经在bootLoaderInit()下打开了LTE的Power
				 */
                delayWithLED(6000);
				if (lte.closeSDP() == false)
				{
					logError("Close Cat.1 SDP failed!");
                    break;
				}
                lte.setSocket(OTA_PROTOCOL, OTA_SERVER_HOST, OTA_SERVER_PORT);
                lte.openSocket();
                if (lte.closeSocket()== false)
				{
					logError("Close Cat.1 SocketBCD failed!");
                    break;
				}
                else
                {
                    logInfo("Close SocketB,C,D successfully.");
                }

                if (lte.save() == false)
                {
                    logError("Init Cat.1 failed!");
                    break;
                }
                else
                {
                    isLTEConfigured = true;
                }
                delayWithLED(6000);
            }

            if (lte.readPropertySequence() == true)
            {
                logVerbose("%s Property",       lte.property.modelType);
                logVerbose("Ver-[%s]",          lte.property.version);
		        logVerbose("IMEI-[%s]",         lte.property.imei);
		        logVerbose("ICCID-[%s]",        lte.property.iccid);
		        logVerbose("CSQ: %dG-[%d]",     lte.property.networkType, lte.property.rssi);
                logVerbose("IP:[%s]",   		lte.property.localIP);
                logVerbose("LinkStatus:[%s]",   lte.property.linkStatus ? ("connected") : ("unconnected"));

                guiWriteProgressBar(0, "HTTP/1.1 GET..");

                stateFSM = FSM_HTTP_HEAD;
            }
        }
        break;

        case FSM_HTTP_HEAD:
        {
            uint8_t  headBuffer[512];
            uint16_t length = 0;
            memset(headBuffer, 0x00, sizeof(headBuffer));

            logVerbose("Get the firmware length by HEAD....");

            length = snprintf((char *)headBuffer, sizeof(headBuffer), "HEAD %s HTTP/1.1\r\nHost: %s\r\n\r\n",
                               otaParameter.firmwarePath, OTA_SERVER_HOST);
            lte.write(headBuffer, length);

            memset(headBuffer, 0x00, sizeof(headBuffer));
            length = lte.read(headBuffer, sizeof(headBuffer), 3200);
            if (length > 0)
            {
                String headResponse;
                for (uint16_t i=0; i<length; i++)
                {
                    headResponse += (char)headBuffer[i];
                }

                if ((headResponse.indexOf("Content-Length: ") != -1) && (headResponse.indexOf("HTTP/1.1 200") != -1))
                {
                    uint16_t positionStart = headResponse.indexOf("Content-Length: ");
                    uint16_t positionEnd   = headResponse.indexOf('\r', positionStart+1);

                    otaParameter.firmwareLength = headResponse.substring(positionStart+16, positionEnd).toInt();

                    logInfo("The firmware = %d bytes", otaParameter.firmwareLength);

                    stateFSM = FSM_HTTP_GET_REQUEST;
                }
                else
                {
                    logError("[OTA-HEAD]Response error or Bad-Request/400!");
                }
            }
            else
            {
                logError("[OTA-HEAD]No Response!");
            }
        }
        break;

        case FSM_HTTP_GET_REQUEST:
        {
            uint8_t  getBuffer[512];
            uint16_t length = 0;
            memset(getBuffer, 0x00, sizeof(getBuffer));

            length = snprintf((char *)getBuffer, sizeof(getBuffer),
                               "GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: insomnia/2023.3.0\r\nRange: bytes=%d-%d\r\nAccept: */*\r\n\r\n",
                               otaParameter.firmwarePath,
                               OTA_SERVER_HOST,
                               otaParameter.startRangeIndex, otaParameter.endRangeIndex);

            // logInfo("HTTP请求内容:\r\n%s", getBuffer)

            logVerbose("Start to request firmware [%d-%d] HTTP/1.1", otaParameter.startRangeIndex, otaParameter.endRangeIndex);
            if(lte.write(getBuffer, length))
            {
                stateFSM = FSM_HTTP_GET_RESPONSE;

                memset(firmwareRxBuffer, 0x00, sizeof(firmwareRxBuffer));
                firmwareRxCount  = 0;
                firmwareExpectRxLength  = otaParameter.endRangeIndex-otaParameter.startRangeIndex+1;
            }
        }
        break;

        case FSM_HTTP_GET_RESPONSE:
        {
            uint16_t firmwareRxLength = lte.read(&firmwareRxBuffer[firmwareRxCount], sizeof(firmwareRxBuffer), 520);

            if (firmwareRxLength > 0)
            {
                firmwareRxCount = firmwareRxCount + firmwareRxLength;  // 下面用firmwareRxCount来实际替代本次接收周期内接收到的数据个数

                if (strstr((char *)firmwareRxBuffer, "HTTP/1.1 206") != NULL)
                {
                    /**
                     * @brief 为避免HEAD多次请求,有可能导致其返回和GET请求的返回混在一起的情况
                     *        此处需要对firmwareRxBuffer进行切割,除去之前HEAD的干扰
                     *        首先找到"HTTP/1.1 206 Partial Content"头部起始位置
                     */
                    uint8_t  *pHead    = (uint8_t *)strstr((char *)firmwareRxBuffer, "HTTP/1.1 206");
                    uint16_t headIndex = pHead-firmwareRxBuffer;            // 计算出"HTTP/1.1 206"的起始位置[下标]

                    uint8_t  *pIndex = (uint8_t *)strstr((char *)&firmwareRxBuffer[headIndex], "\r\n\r\n");
                    if (pIndex != NULL)
                    {
                        uint16_t index         = (pIndex-firmwareRxBuffer)+4;   // 下标指向除去Http包结尾后payload的第一个字节
                        uint16_t payloadLength = firmwareRxCount-index;         // 计算出实际payload firmware的长度

                        if (payloadLength < firmwareExpectRxLength)         // 实际接收字节数小于预期,继续接收
                        {
                            logWarning("Firmware is not fully received, Except[%d]-Actual[%d]", firmwareExpectRxLength, firmwareRxLength);
                            break;
                        }
                        else if (payloadLength > firmwareExpectRxLength)    // 实际接收字节数超出预期,重新请求
                        {
                            logError("HTTP/1.1 404 实际接收字节数超出预期,Except[%d]-Actual[%d]", firmwareExpectRxLength, firmwareRxLength);
                            logDebug("headIndex=[%d],index=[%d],payloadLength=[%d],firmwareExpectRxLength =[%d]",
                                                headIndex, index, payloadLength, firmwareExpectRxLength);

                            // logHexDump(firmwareRxBuffer, firmwareRxCount);

                            stateFSM = FSM_HTTP_GET_REQUEST;
                            break;
                        }
                        else
                        {
                            /* 取出payload,注意此处是从\r\n\r\n后的第一个字节开始的,也即index */
                            for (uint16_t i=0; i<payloadLength; i++)
                            {
                                firmwareRBuffer.lockedPush(firmwareRxBuffer[i+index]);
                            }
                            logInfo("Downloading [%d]bytes from the server......", payloadLength);

                            errGetResponseCount  = 0;     // 成功接收一次则将错误累计计数清零
                            errGetReRequestCount = 0;

                            stateFSM = FSM_FLASH_WRITE;
                        }

                    }
                    else
                    {
                        logError("[OTA-GET]Response error, No[CRLF]!");
                    }
                }
                else if (strstr((char *)firmwareRxBuffer, "HTTP/1.1 4") != NULL)    // Bad Request 404/error 直接重新请求
                {
                    stateFSM = FSM_HTTP_GET_REQUEST;
                    break;
                }
                else
                {
                    logError("[OTA-GET]Response error, No[HTTP/1.1 2]!");           // 未必要重新请求,可能是请求的字节数不够
                }
            }
            else
            {
                if (++errGetResponseCount >= 5)
                {
                    /* 重新请求下载固件 */
                    errGetResponseCount = 0;
                    stateFSM = FSM_HTTP_GET_REQUEST;

                    if (++errGetReRequestCount >= 3)    // 重新下载固件两次依然失败,那么重启整个系统
                    {
                        errGetReRequestCount = 0;
                        rebootSystem();
                    }

                }
                logError("[OTA-GET]No Response[%d]!", errGetResponseCount);
            }
        }
        break;

        case FSM_FLASH_WRITE:
        {
            uint8_t  payloadBuffer[OTA_BUFFER_SIZE];
            uint16_t payloadBufferLength = 0;
            memset(payloadBuffer, 0x00, sizeof(payloadBuffer));

            payloadBufferLength = firmwareRBuffer.size();
            for (uint16_t i=0; i<payloadBufferLength; i++)
            {
                firmwareRBuffer.lockedPop(payloadBuffer[i]);
            }
            firmwareRBuffer.clear();
            // logHexDump(payloadBuffer, payloadBufferLength);

            bool writeResult = bootLoaderWriteFlash(USR_APP_ADDRESS+otaParameter.firmwareRxCount, payloadBuffer, payloadBufferLength);
            /* 控制下次GET-Request的请求范围,以及判断是否全部接收完毕 */
            otaParameter.firmwareRxCount += payloadBufferLength;

            guiWriteProgressBar((uint8_t)(otaParameter.firmwareRxCount*100/otaParameter.firmwareLength), "Write Flash...");

            logInfo("[%d%%]Write [%d]bytes to Flash @[0x%08X]--S[%d], Total receive [%d]bytes\r\n",
                     (uint8_t)(otaParameter.firmwareRxCount*100/otaParameter.firmwareLength),
                     payloadBufferLength,
                     USR_APP_ADDRESS+otaParameter.firmwareRxCount,
                     writeResult,
                     otaParameter.firmwareRxCount);

            if (otaParameter.firmwareRxCount == otaParameter.firmwareLength)
            {
                logInfo("OTA completely!");

                guiWriteProgressBar(100, "OTA Succeed!");

                setOTAUpgradeFlag(OTA_FLAG_CLEAR);
                setOTANotifyFlag();                     // 这个仅仅放在更新完毕后的地方,只有在成功更新后通知APP,避免网卡不断置位
                clearOTAJumpToApplicationTryCount();    // Application是否能成功跳转,从0开始计数

                // if ((otaParameter.crc32Value == bootLoaderCRC32Verify(otaParameter.firmwareLength) || (otaParameter.crc32Value == 0)))
                // {
                //     logInfo("OTA Succeed!");
                //     guiWriteProgressBar(100, "OTA Succeed!");

                //     setOTAUpgradeFlag(OTA_FLAG_CLEAR);
                //     setOTANotifyFlag();                     // 这个仅仅放在更新完毕后的地方,只有在成功更新后通知APP,避免网卡不断置位
                //     clearOTAJumpToApplicationTryCount();    // Application是否能成功跳转,从0开始计数
                // }
                // else
                // {
                //     logInfo("OTA Failed, CRC Error!");
                //     guiWriteProgressBar(100, "OTA Failed, CRC Error!");
                // }

                rebootSystem();
            }
            else if (otaParameter.firmwareRxCount < otaParameter.firmwareLength)
            {
                otaParameter.startRangeIndex = otaParameter.endRangeIndex+1;

                if ((otaParameter.firmwareLength-otaParameter.firmwareRxCount) >= OTA_HTTP_GET_SIZE_BYTE_PER)
                {
                    otaParameter.endRangeIndex += OTA_HTTP_GET_SIZE_BYTE_PER;
                }
                else
                {
                    otaParameter.endRangeIndex = otaParameter.firmwareLength - 1;
                }

                stateFSM = FSM_HTTP_GET_REQUEST;
            }
            else
            {
                logError("errCode[402]");   // firmware累计接收到的数据大于其自身长度
            }
        }
        break;

        default:
            break;
    }

    /**
     * @brief 当在一个状态机里面循环超过10次
     *        这意味着其陷入了死循环,需要重启启动
     */
    if (lastStateFSM == stateFSM)
    {
        errCycleCount++;
        if (errCycleCount == 10)
        {
            errCycleCount = 0;
            rebootSystem();
        }
    }
    else
    {
        if (errCycleCount > 0)
        {
            errCycleCount--;
        }
    }
    lastStateFSM = stateFSM;

    IWatchdog.reload();     // 状态机每循环一次,就执行一次喂狗
}

void guiInit(void)
{
#ifdef ULTIMATE
    tft.fillScreen(TFT_ST7789::COLOR_BLACK);

    const char displayBuff[] = "BootLoader";

    tft.drawString((TFT_WIDTH - strlen(displayBuff) * 12) / 2, ceil(TFT_HEIGHT * 0.15), displayBuff, TFT_ST7789::COLOR_GBLUE, TFT_ST7789::COLOR_BLACK, 24);
#endif

#ifdef SIMPLE
    tft.fillScreen(TFT_ST7735::COLOR_BLACK);

    const char displayBuff[] = "BootLoader";

    tft.drawString((TFT_WIDTH - strlen(displayBuff) * 8) / 2, 0, displayBuff, TFT_ST7735::COLOR_GBLUE, TFT_ST7735::COLOR_BLACK, 16);
#endif
}

void guiWriteProgressBar(const uint8_t value, const char *str)
{
#ifdef ULTIMATE
    uint16_t barLength = ceil(TFT_WIDTH * 0.85f);
    uint16_t barWidth  = ceil(TFT_HEIGHT * 0.1f);

    uint16_t startX = ((TFT_WIDTH - barLength) % 2) ? (TFT_WIDTH  - barLength + 1) / 2 : (TFT_WIDTH - barLength) / 2;
    uint16_t startY = ((TFT_HEIGHT - barWidth) % 2) ? (TFT_HEIGHT - barWidth + 1) / 2 : (TFT_HEIGHT - barWidth) / 2;
    startY += ceil(TFT_HEIGHT * 0.05);

    uint8_t validValue = (value >= 100) ? 100 : value;
    uint8_t BarLengthOffset = (validValue == 100) ? (barLength - 3) : ceil((barLength - 3) * 0.01f * validValue);

    char displayBuff[5] = " ";
    snprintf(displayBuff, 5, " %d%% ", validValue);
    tft.drawString(startX + (barLength - (strlen(displayBuff) * 8)) / 2, startY - 16, displayBuff, TFT_ST7789::COLOR_WHITE, TFT_ST7789::COLOR_BLACK, 16);

    tft.drawRect(startX, startY, startX + barLength, startY + barWidth, TFT_ST7789::COLOR_WHITE);
    tft.fillRect(startX + 2, startY + 2, startX + 2 + BarLengthOffset, startY + 2 + barWidth - 3, TFT_ST7789::COLOR_GREEN);
    tft.fillRect(startX + 2 + BarLengthOffset, startY + 2, startX + 2 + barLength - 3, startY + 2 + barWidth - 3, TFT_ST7789::COLOR_BLACK);
    tft.drawRect(startX + 1, startY + 1, startX + barLength - 1, startY + barWidth - 1, TFT_ST7789::COLOR_WHITE);

    uint16_t strIndex = floor((TFT_WIDTH - strlen(str) * 12) / 2/12);
    char strDisplayBuff[] = "                    ";// 240/12 = 20 space key
    memcpy(&strDisplayBuff[strIndex],str,(strlen(str)<20)?strlen(str):19);
    tft.drawString(0, startY + ceil(TFT_HEIGHT * 0.25), strDisplayBuff, TFT_ST7789::COLOR_WHITE, TFT_ST7789::COLOR_BLACK, 24);
#endif

#ifdef SIMPLE
    uint16_t barLength = ceil(TFT_WIDTH * 0.85f);
    uint16_t barWidth  = ceil(TFT_HEIGHT * 0.1f);

    uint16_t startX = ((TFT_WIDTH - barLength) % 2) ? (TFT_WIDTH - barLength + 1) / 2 : (TFT_WIDTH - barLength) / 2;
    uint16_t startY = ((TFT_HEIGHT - barWidth) % 2) ? (TFT_HEIGHT - barWidth + 1) / 2 : (TFT_HEIGHT - barWidth) / 2;
    startY += 4;

    uint8_t validValue = (value >= 100) ? 100 : value;
    uint8_t BarLengthOffset = (validValue == 100) ? (barLength - 3) : ceil((barLength - 3) * 0.01f * validValue);

    char displayBuff[5] = " ";
    snprintf(displayBuff, 5, " %d%% ", validValue);
    tft.drawString(startX + (barLength - (strlen(displayBuff) * 8)) / 2, startY - 16, displayBuff, TFT_ST7735::COLOR_WHITE, TFT_ST7735::COLOR_BLACK, 16);

    tft.drawRect(startX, startY, startX + barLength, startY + barWidth, TFT_ST7735::COLOR_WHITE);
    tft.fillRect(startX + 2, startY + 2, startX + 2 + BarLengthOffset, startY + 2 + barWidth - 3, TFT_ST7735::COLOR_GREEN);
    tft.fillRect(startX + 2 + BarLengthOffset, startY + 2, startX + 2 + barLength - 3, startY + 2 + barWidth - 3, TFT_ST7735::COLOR_BLACK);
    tft.drawRect(startX + 1, startY + 1, startX + barLength - 1, startY + barWidth - 1, TFT_ST7735::COLOR_WHITE);

    uint16_t strIndex = floor((TFT_WIDTH - strlen(str) * 8) / 2 / 8);
    char strDisplayBuff[] = "                              "; // 240/8 = 30 space key
    memcpy(&strDisplayBuff[strIndex], str, (strlen(str) < 30) ? strlen(str) : 29);
    tft.drawString(0, startY + 20, strDisplayBuff, TFT_ST7735::COLOR_GRAYBLUE, TFT_ST7735::COLOR_BLACK, 16);
#endif
}
